﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace Gurucore.Framework.Core.Util
{
	public static class CloneUtil
	{
		public static object DeepClone(this object p_oObject)
		{
			Type oType = p_oObject.GetType();
			object oCopy = null;
			object[] arrClonableAttr = oType.GetCustomAttributes(typeof(ClonableAttribute), true);

			if ((oType.IsGenericType) && (oType.GetGenericTypeDefinition() == typeof(List<>))) //List<>
			{
				oCopy = Activator.CreateInstance(oType);

				int nCount = (int)oType.GetProperty("Count").GetValue(p_oObject, null);  
				MethodInfo oGetItemMethod = oType.GetMethod("get_Item");
				MethodInfo oAddMethod = oType.GetMethod("Add");
				for (int nIndex = 0; nIndex < nCount; nIndex++)  
				{
					object oItem = oGetItemMethod.Invoke(p_oObject, new object[] { nIndex });
					oAddMethod.Invoke(oCopy, new object[] { oItem.DeepClone() });
				}
			}
			else if ((oType.IsGenericType) && (oType.GetGenericTypeDefinition() == typeof(Dictionary<,>))) //Dictionary
			{
				oCopy = Activator.CreateInstance(oType);

				object oEnumerator = oType.GetMethod("GetEnumerator").Invoke(p_oObject, null);
				MethodInfo oNextMethod = oEnumerator.GetType().GetMethod("MoveNext");
				PropertyInfo oCurrentProperty = oEnumerator.GetType().GetProperty("Current");
				MethodInfo oAddMethod = oType.GetMethod("Add");

				while ((bool)oNextMethod.Invoke(oEnumerator, null))
				{
					object oItem = oCurrentProperty.GetValue(oEnumerator, null);
					object oKey = oItem.GetType().GetProperty("Key").GetValue(oItem, null);
					object oValue = oItem.GetType().GetProperty("Value").GetValue(oItem, null);
					oType.GetMethod("Add").Invoke(oCopy, new object[] { oKey.DeepClone(), oValue.DeepClone() });
				}
			}
			else if (p_oObject is ICloneable)
			{
				//in case of calling directly on IClonable objects
				oCopy = ((ICloneable)p_oObject).Clone();
			}
			else if (oType.IsPrimitive)
			{
				oCopy = p_oObject;
			}
			else if (arrClonableAttr.Length > 0)
			{
				oCopy = Activator.CreateInstance(oType);

				PropertyInfo[] arrProperty = oType.GetProperties();
				foreach (PropertyInfo oProperty in arrProperty)
				{
					object oPropertyValue = oProperty.GetValue(p_oObject, null);
					if (oPropertyValue != null)
					{
						//recursive
						oProperty.SetValue(oCopy, oPropertyValue.DeepClone(), null);
					}
				}
			}
			return oCopy;
		}
	}
}
